Spring Cloud Alibaba 简介 spring cloud 和 spring cloud alibaba 的关系 简单来说,Spring Cloud 和 Spring Cloud Alibaba 之间是 “标准规范”与 “具体实现” 的关系。如果把 Spring Cloud 比作一套分布式微服务架构的 “官方设计图纸”,那么 Spring Cloud Alibaba 就是由阿里巴巴团队基于这套图纸,结合自身双十一超大规模高并发的实战经验,设计出的一套高性能 “具体施工方案” 和 “全家桶组件”。
Spring Cloud 并不是一个单一的框架,而是一个微服务治理的标准规范和生态总称。它定义了一套微服务架构所必需的标准组件规范,比如:
注册中心(服务发现)
配置中心
分布式路由(网关)
断路器(熔断降级)
负载均衡
远程调用
随着早年 Spring Cloud 官方主推的 Netflix 套件(如 Eureka、Hystrix、Zuul)全面进入停更维护状态,市场上急需新一代高性能组件。阿里巴巴在 2018 年将其内部经过考验的微服务组件开源,并加入了 Spring Cloud 官方孵化器,成为了 Spring Cloud 的一个官方子项目。它实现了 Spring Cloud 定义的所有标准微服务规范,并用阿里自研的全新高性能组件进行了替换。
经典组件与阿里组件的演进表:
传统 Spring Cloud(基于 Netflix 套件等)运维成本高。比如要搭建注册中心和配置中心,你需要自己 new 两个 Spring Boot 项目,分别引入 Eureka Server 和 Config Server 依赖,配置繁琐,且极其消耗内存。而 Spring Cloud Alibaba 诞生于电商高并发、双十一流量大潮的真实工业级场景,有着开箱即用和高性能的特点:
一个 Nacos 直接把注册中心和配置中心全干了,而且 Nacos 是开箱即用的独立独立程序(类似于中间件),不需要你自己在 Java 里去写 Server 端。
强大的可视化:Sentinel 和 Nacos 都自带极度舒适的 Web 管理控制台,流控规则、降级策略直接在浏览器里点点鼠标就能 “秒级动态生效”,而不需要像 Hystrix 那样去硬编码写一堆注解参数。
在目前的国内企业选型中,Spring Cloud 提供的是微服务治理的灵魂和接口规范(比如 @EnableDiscoveryClient 注解),Spring Cloud Alibaba 则是目前国内最普及、生态最活跃、性能最强悍的肉身实现。
版本依赖与兼容性 因为 Spring Cloud Alibaba 和 Spring Cloud 是子项目与主项目的关系,在开发时绝对不能随便乱配版本,否则会遭遇极其痛苦的类冲突和启动崩溃。
版本代号的跟随:Spring Cloud 的版本通常以 202x.x.x(如 2022.0.0)这样的年份形式命名。
依赖引入规范:在 Maven 中,正确的姿势是先锁定 Spring Boot 的版本,再锁定兼容的 Spring Cloud 版本,最后引入 Spring Cloud Alibaba 的 BOM(版本管理)。
为了防止版本打架,可以直接去 Github spring-cloud-alibaba 去查看当前最稳健的 “三合一” 版本对齐表。
Spring Cloud 和 Dubbo 的说明 为什么还要 Spring Cloud 可能有人会问:既然项目中使用了 dubbo 这套微服务架构,为什么我们还要使用 springcloud?一句话总结核心原因就是:Dubbo 是极其优秀的“专科医生”,主攻高性能 RPC 通信;而 Spring Cloud 是一个全能的 “医院生态”,提供整套微服务治理全家桶。使用 Spring Cloud 是为了用它的生态去补齐 Dubbo 在 RPC 之外的治理短板。具体来说,如果一个企业只用纯 Dubbo 架构,很快就会在开发中面临以下 “生态荒”:
统一网关层(Gateway)的缺失
Dubbo 内部是走 RPC(如 Netty/Triple)二进制流或长连接通信的。但是前端(App/H5/小程序)或者第三方系统是无法直接发起 Dubbo RPC 请求的,它们只认标准的 HTTP Restful 接口。
Dubbo 官方原生并没有提供类似 Spring Cloud Gateway 这样成熟、强大、能完美与 Spring 生态鉴权(如 Spring Security/OAuth2)集成的网关。我们需要利用 Spring Cloud Gateway 挡在最前面,作为流量入口负责路由、限流和安全验签,然后再由网关或者内部的 Web 模块将请求转化为 Dubbo RPC 投递给内网的 Service。
配置中心与动态刷新机制
虽然 Dubbo 3.x 支持动态配置,但它更倾向于治理 Dubbo 自身的参数(如路由规则、限流比例)。
业务开发中大量的业务配置(如数据库连接池、业务开关、活动优惠券参数)需要一整套成熟的统一管理和动态刷新机制。Spring Cloud Alibaba Nacos Config 对 Spring 内部 @Value 和 @RefreshScope 的原生支持无缝且丝滑,这是传统 RPC 框架不具备的业务级治理能力。
分布式链路追踪与监控
微服务线上一旦报错,调用链可能跨越七八个服务。
Spring Cloud 生态有极其成熟的 Micrometer / Spring Cloud Sleuth / SkyWalking / Zipkin 整合方案。它能自动拦截 HTTP 请求,并把 TraceId 一路隐式传递到底层 Dubbo 的 RPC 上下文中,让整条跨协议的调用链在监控大屏上一览无遗。
异步事件驱动架构
微服务之间除了同步 RPC 调用,还需要大量的异步削峰(如订单支付成功,发送 MQ 通知库存、积分、短信服务)。
Spring Cloud 提供了 Spring Cloud Stream 这种高级抽象,让你只需要写几行注解就能随意切换 RabbitMQ、Kafka 或 RocketMQ。Dubbo 作为 RPC 框架,本身是不涉及任何 MQ 消息驱动封装的。
两者结合后的企业级架构拓扑:
实际技术选型
如果只是纯内部老系统改造,或者不需要复杂的网关和 MQ 业务:单挂一个 Dubbo + Nacos 就能跑得非常欢快,架构更轻量。
如果并发量不大,团队全员熟悉 Spring 生态:直接用全套 Spring Cloud (用 OpenFeign 做 HTTP 通信) 是开发速度最快的。
如果面临高并发场景,既要 Spring 的庞大生态,又要极端的通信性能:Spring Cloud 做皮(外围治理),Dubbo 做骨(内网 RPC),是目前最最可行的方案。
演示项目源码 项目的说明 本项目旨在说明 spring cloud alibaba 结合 spring cloud、spring boot 的具体使用,你也可以将其当做项目构建的基础代码来使用。项目中会用到三种远程调用技术:
基于 RestTemplate 的原始的 HTTP 方式的调用
基于 OpenFeign 的改进版 HTTP 方式的调用
基于 dubbo 的高性能远程服务调用
服务的注册发现、配置中心采用 Nacos。此外我们还会演示调用中遇到的其他关键基础技术,例如 Nacos 的基本使用、服务的超时设置、负载均衡、各自的拦截器等。
项目模块使用 maven 进行统一管理,整个演示项目分为三个模块:
zdemo-scloud-api:定义公共类和接口
zdemo-scloud-order:服务的提供者,提供 dubbo 或 http 接口给下游的 user 调用
zdemo-scloud-user:普通的服务的调用者
zdemo-scloud-order 和 zdemo-scloud-user 服务注册和配置中心的体现:
父项目POM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.zdemo.scloud</groupId > <artifactId > zdemo-scloud-parent</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > pom</packaging > <modules > <module > zdemo-scloud-api</module > <module > zdemo-scloud-order</module > <module > zdemo-scloud-user</module > </modules > <properties > <maven.compiler.source > 17</maven.compiler.source > <maven.compiler.target > 17</maven.compiler.target > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <spring-boot.version > 3.3.0</spring-boot.version > <spring-cloud.version > 2023.0.1</spring-cloud.version > <spring-cloud-alibaba.version > 2023.0.1.2</spring-cloud-alibaba.version > <dubbo.version > 3.2.19</dubbo.version > <mybatis-plus.version > 3.5.7</mybatis-plus.version > <hutool.version > 5.8.26</hutool.version > <lombok.version > 1.18.44</lombok.version > <mapstruct.version > 1.5.5.Final</mapstruct.version > </properties > <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > ${spring-boot.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-dependencies</artifactId > <version > ${spring-cloud.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-alibaba-dependencies</artifactId > <version > ${spring-cloud-alibaba.version}</version > <type > pom</type > <scope > import</scope > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-spring-boot-starter</artifactId > <version > ${dubbo.version}</version > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-serialization-fastjson2</artifactId > <version > ${dubbo.verison}</version > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-rpc-triple</artifactId > <version > ${dubbo.verison}</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-spring-boot3-starter</artifactId > <version > ${mybatis-plus.version}</version > </dependency > <dependency > <groupId > cn.hutool</groupId > <artifactId > hutool-all</artifactId > <version > ${hutool.version}</version > </dependency > <dependency > <groupId > org.mapstruct</groupId > <artifactId > mapstruct</artifactId > <version > ${mapstruct.version}</version > </dependency > </dependencies > </dependencyManagement > <dependencies > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > <scope > provided</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency > </dependencies > <build > <pluginManagement > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <excludes > <exclude > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </exclude > </excludes > </configuration > </plugin > </plugins > </pluginManagement > <plugins > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <configuration > <source > ${maven.compiler.source}</source > <target > ${maven.compiler.target}</target > <parameters > true</parameters > <annotationProcessorPaths > <path > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > ${lombok.version}</version > </path > <path > <groupId > org.mapstruct</groupId > <artifactId > mapstruct-processor</artifactId > <version > ${mapstruct.version}</version > </path > </annotationProcessorPaths > <parameters > true</parameters > <compilerArgs > <arg > -parameters</arg > </compilerArgs > </configuration > </plugin > </plugins > </build > <profiles > <profile > <id > dev</id > <properties > <profile.active > dev</profile.active > </properties > <activation > <activeByDefault > true</activeByDefault > </activation > </profile > <profile > <id > test</id > <properties > <profile.active > test</profile.active > </properties > </profile > <profile > <id > prod</id > <properties > <profile.active > prod</profile.active > </properties > </profile > </profiles > <repositories > <repository > <id > alibaba-maven-central</id > <url > https://maven.pkg.github.com/alibaba/spring-cloud-alibaba</url > <snapshots > <enabled > false</enabled > </snapshots > <releases > <enabled > true</enabled > </releases > </repository > </repositories > <pluginRepositories > <pluginRepository > <id > alibaba-maven-central-plugins</id > <url > https://maven.pkg.github.com/alibaba/spring-cloud-alibaba</url > <snapshots > <enabled > false</enabled > </snapshots > <releases > <enabled > true</enabled > </releases > </pluginRepository > </pluginRepositories > </project >
API 公共模块
依赖配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.zdemo.scloud</groupId > <artifactId > zdemo-scloud-parent</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > zdemo-scloud-api</artifactId > <dependencies > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-web</artifactId > </dependency > </dependencies > </project >
公共类 OrderDTO
1 2 3 4 5 6 7 8 @Data @AllArgsConstructor @NoArgsConstructor public class OrderDTO implements Serializable { private String orderNo; private BigDecimal amount; private String productName; }
接口定义 dubbo 接口的定义:
1 2 3 4 5 6 7 8 9 10 package zdemo.scloud.api.service.dubbo;import zdemo.scloud.api.dto.OrderDTO;public interface OrderService { OrderDTO getOrderDetails (String orderNo) ; }
feign 接口的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package zdemo.scloud.api.service.feign;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import zdemo.scloud.api.dto.OrderDTO;@FeignClient(value = "zdemo-scloud-order", path = "/order") public interface OrderFeignService { @GetMapping("/details") OrderDTO getUserOrderDetails (@RequestParam("orderNo") String orderNo) ; }
FeignConfig(可选)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package zdemo.scloud.api.service.feign;import feign.Logger;import feign.Request;import org.springframework.context.annotation.Bean;public class FeignConfig { @Bean public Logger.Level feignLoggerLevel () { return Logger.Level.FULL; } @Bean public Request.Options feignRequestOptions () { return new Request .Options(5000 ,10000 ); } }
服务提供者 order
依赖配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.zdemo.scloud</groupId > <artifactId > zdemo-scloud-parent</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > zdemo-scloud-order</artifactId > <dependencies > <dependency > <groupId > com.zdemo.scloud</groupId > <artifactId > zdemo-scloud-api</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-spring-boot-starter</artifactId > <version > ${dubbo.version}</version > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-rpc-triple</artifactId > <version > ${dubbo.version}</version > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-serialization-fastjson2</artifactId > <version > ${dubbo.version}</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
日志配置 src/main/resources/logback-spring.xml(打印 traceId)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?xml version="1.0" encoding="UTF-8" ?> <configuration scan ="true" scanPeriod ="60 seconds" > <property name ="LOG_PATH" value ="./logs" /> <property name ="APP_NAME" value ="order" /> <property name ="CONSOLE_LOG_PATTERN" value ="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:- } --- [%15.15thread] [%X{traceId}] %-40.40logger{39} : %m%n" /> <property name ="FILE_LOG_PATTERN" value ="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:- } --- [%thread] [%X{traceId}] %logger{50} - [%method,%line] - %m%n" /> <appender name ="CONSOLE" class ="ch.qos.logback.core.ConsoleAppender" > <encoder > <pattern > ${CONSOLE_LOG_PATTERN}</pattern > <charset > UTF-8</charset > </encoder > </appender > <appender name ="INFO_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_PATH}/${APP_NAME}-sys-info.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" > <fileNamePattern > ${LOG_PATH}/archive/${APP_NAME}-sys-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern > <maxFileSize > 100MB</maxFileSize > <maxHistory > 30</maxHistory > <totalSizeCap > 30GB</totalSizeCap > </rollingPolicy > <encoder > <pattern > ${FILE_LOG_PATTERN}</pattern > <charset > UTF-8</charset > </encoder > <filter class ="ch.qos.logback.classic.filter.LevelFilter" > <level > ERROR</level > <onMatch > DENY</onMatch > <onMismatch > ACCEPT</onMismatch > </filter > </appender > <appender name ="ERROR_FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_PATH}/${APP_NAME}-sys-error.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" > <fileNamePattern > ${LOG_PATH}/archive/${APP_NAME}-sys-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern > <maxFileSize > 100MB</maxFileSize > <maxHistory > 60</maxHistory > </rollingPolicy > <encoder > <pattern > ${FILE_LOG_PATTERN}</pattern > <charset > UTF-8</charset > </encoder > <filter class ="ch.qos.logback.classic.filter.ThresholdFilter" > <level > ERROR</level > </filter > </appender > <root level ="INFO" > <appender-ref ref ="CONSOLE" /> <appender-ref ref ="INFO_FILE" /> <appender-ref ref ="ERROR_FILE" /> </root > </configuration >
核心配置 src/main/resources/application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 server: port: 8081 spring: application: name: zdemo-scloud-order cloud: nacos: discovery: server-addr: 192.168 .1 .149 :8848 username: nacos password: nacos namespace: prod-zdemo cluster-name: DEFAULT config: server-addr: 192.168 .1 .149 :8848 username: nacos password: nacos file-extension: yml namespace: prod-zdemo cluster-name: DEFAULT config: import: - optional:nacos:${spring.application.name}.${spring.cloud.nacos.config.file-extension} dubbo: application: register-mode: INSTANCE name: ${spring.application.name}-rpc qos-enable: true qos-port: 22222 registry: address: nacos://192.168.1.149:8848?namespace=prod-zdemo username: nacos password: nacos protocol: name: tri port: 20881 serialization: fastjson2 prefer-serialization: fastjson2 logging: level: org.apache.dubbo: INFO org.apache.dubbo.rpc.filter: ERROR org.apache.dubbo.config.deploy.DefaultMetricsServiceExporter: ERROR
启动类 OrderApplication
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package zdemo.scloud.order;import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@SpringBootApplication @EnableDiscoveryClient @EnableDubbo public class OrderApplication { public static void main (String[] args) { SpringApplication.run(OrderApplication.class, args); } }
feign 接口的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package zdemo.scloud.order.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import zdemo.scloud.api.dto.OrderDTO;import java.math.BigDecimal;@Slf4j @RestController @RequestMapping("/order") public class OrderController { @Value("${server.port}") private Integer port; @GetMapping("/details") public OrderDTO getUserOrderDetails (@RequestParam String orderNo) { log.info("收到客户端订单:{}" , orderNo); try { Thread.sleep(3000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } return new OrderDTO (orderNo, new BigDecimal ("200.00" ), "Owlias教程:" + port); } }
dubbo 接口的实现 OrderServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package zdemo.scloud.order.service;import lombok.extern.slf4j.Slf4j;import org.apache.dubbo.config.annotation.DubboService;import zdemo.scloud.api.dto.OrderDTO;import zdemo.scloud.api.service.dubbo.OrderService;import java.math.BigDecimal;@Slf4j @DubboService public class OrderServiceImpl implements OrderService { @Override public OrderDTO getOrderDetails (String orderNo) { log.info("收到 RPC 请求,订单号: {}" , orderNo); return new OrderDTO (orderNo, new BigDecimal ("199.00" ), "Owlias教程" ); } }
dubbo 服务端 Filter 给服务提供端定义一个过滤器,用于链路追踪(可选)。
首先在服务提供端定义一个过滤器 ProviderDubboFilter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package zdemo.scloud.order.filter;import org.apache.dubbo.common.constants.CommonConstants;import org.apache.dubbo.common.extension.Activate;import org.apache.dubbo.rpc.*;import org.slf4j.MDC;import java.util.concurrent.ThreadLocalRandom;@Activate(group = CommonConstants.PROVIDER) public class ProviderDubboFilter implements Filter { private static final String TRACE_ID_KEY = "traceId" ; @Override public Result invoke (Invoker<?> invoker, Invocation invocation) throws RpcException { String traceId = (String) RpcContext.getServiceContext().getObjectAttachment(TRACE_ID_KEY); if (traceId == null || traceId.isEmpty()) { traceId = Long.toHexString(ThreadLocalRandom.current().nextLong()); if (traceId.length() < 16 ) { traceId = String.format("%16s" , traceId).replace(' ' , '0' ); } } MDC.put(TRACE_ID_KEY, traceId); try { return invoker.invoke(invocation); } finally { MDC.remove(TRACE_ID_KEY); } } }
其次,在服务端的 src/main/resources/META-INF/dubbo 目录中定义一个名为 “org.apache.dubbo.rpc.Filter” 的文件,文件的内容是拦截器列表(dubbo的拦截器是通过SPI进行发现和注册的):
1 providerDubboFilter =zdemo.scloud.order.filter.ProviderDubboFilter
服务调用者 user
依赖配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.zdemo.scloud</groupId > <artifactId > zdemo-scloud-parent</artifactId > <version > 1.0-SNAPSHOT</version > </parent > <artifactId > zdemo-scloud-user</artifactId > <dependencies > <dependency > <groupId > com.zdemo.scloud</groupId > <artifactId > zdemo-scloud-api</artifactId > <version > ${project.version}</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo-spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > <optional > true</optional > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
日志配置与 order 模块基本一致(略 )。
核心配置 src/main/resources/application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 server: port: 8180 spring: application: name: zdemo-scloud-user cloud: nacos: discovery: server-addr: 192.168 .1 .149 :8848 username: nacos password: nacos namespace: prod-zdemo cluster-name: DEFAULT config: server-addr: 192.168 .1 .149 :8848 username: nacos password: nacos file-extension: yml namespace: prod-zdemo cluster-name: DEFAULT loadbalancer: cache: enabled: false nacos: enabled: true openfeign: client: config: default: logger-level: none connect-timeout: 5000 read-timeout: 5000 zdemo-scloud-order: logger-level: full connect-timeout: 5000 read-timeout: 11000 request-interceptors: - zdemo.scloud.user.filter.CustomOpenFeignInterceptor config: import: - optional:nacos:${spring.application.name}.${spring.cloud.nacos.config.file-extension} dubbo: application: name: ${spring.application.name}-rpc qos-enable: true qos-port: 22233 registry: address: nacos://192.168.1.149:8848?namespace=prod-zdemo username: nacos password: nacos logging: level: org.apache.dubbo: INFO org.apache.dubbo.rpc.filter: ERROR org.apache.dubbo.config.deploy.DefaultMetricsServiceExporter: ERROR zdemo.scloud.api.service.feign: DEBUG
启动类 UserApplication
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package zdemo.scloud.user;import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication @EnableDiscoveryClient @EnableDubbo @EnableFeignClients(basePackages = {"zdemo.scloud.api.service.feign"}) public class UserApplication { public static void main (String[] args) { SpringApplication.run(UserApplication.class, args); } }
应用 controller 层 UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 package zdemo.scloud.user.controller;import jakarta.annotation.Resource;import lombok.extern.slf4j.Slf4j;import org.apache.dubbo.config.annotation.DubboReference;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import zdemo.scloud.api.dto.OrderDTO;import zdemo.scloud.api.service.feign.OrderFeignService;import zdemo.scloud.api.service.dubbo.OrderService;@Slf4j @RestController public class UserController { @DubboReference(loadbalance = "roundrobin") private OrderService orderService; @Resource private RestTemplate restTemplate; @Resource private OrderFeignService orderFeignService; @GetMapping("/dubbo/user/order") public OrderDTO getUserOrderDetailsUsingDubbo (@RequestParam String orderNo) { log.info("使用 dubbo 发起 RPC 请求,订单号: {}" , orderNo); return orderService.getOrderDetails(orderNo); } @GetMapping("/http/user/order") public OrderDTO getUserOrderDetailsUsingRestTemplate (@RequestParam String orderNo) { log.info("使用 http 发起 RPC 请求,订单号: {}" , orderNo); String serviceName = "zdemo-scloud-order" ; String url = "http://" + serviceName + "/order/details?orderNo=" + orderNo; System.out.println("正在通过 LoadBalancer 准备投递请求至: " + url); return restTemplate.getForObject(url, OrderDTO.class); } @GetMapping("/feign/user/order") public OrderDTO getUserOrderDetailsUsingOpenFeign (@RequestParam String orderNo) { log.info("使用 feign 发起 RPC 请求,订单号: {}" , orderNo); return orderFeignService.getUserOrderDetails(orderNo); } }
普通应用的过滤器 TraceIdMdcFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package zdemo.scloud.user.filter;import jakarta.servlet.*;import jakarta.servlet.http.HttpServletRequest;import org.slf4j.MDC;import org.springframework.stereotype.Component;import java.io.IOException;import java.util.concurrent.ThreadLocalRandom;@Component public class TraceIdMdcFilter implements Filter { private static final String TRACE_ID_KEY = "traceId" ; @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; String traceId = request.getHeader(TRACE_ID_KEY); if (traceId == null || traceId.isEmpty()) { traceId = Long.toHexString(ThreadLocalRandom.current().nextLong()); if (traceId.length() < 16 ) { traceId = String.format("%16s" , traceId).replace(' ' , '0' ); } } MDC.put(TRACE_ID_KEY, traceId); try { filterChain.doFilter(servletRequest, servletResponse); } finally { MDC.remove(TRACE_ID_KEY); } } }
dubbo 调用的支持 通过以下方式使用 dubbo 接口:
1 2 3 4 5 6 7 8 @DubboReference(loadbalance = "roundrobin") private OrderService orderService;@GetMapping("/dubbo/user/order") public OrderDTO getUserOrderDetailsUsingDubbo (@RequestParam String orderNo) { log.info("使用 dubbo 发起 RPC 请求,订单号: {}" , orderNo); return orderService.getOrderDetails(orderNo); }
dubbo 调用端 Filter 给调用端增加一个过滤器,用于在服务发起时透传 traceId(可选)。
ConsumerDubboFilter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package zdemo.scloud.user.filter;import org.apache.dubbo.common.constants.CommonConstants;import org.apache.dubbo.common.extension.Activate;import org.apache.dubbo.rpc.*;import org.slf4j.MDC;@Activate(group = CommonConstants.CONSUMER) public class ConsumerDubboFilter implements Filter { private static final String TRACE_ID_KEY = "traceId" ; @Override public Result invoke (Invoker<?> invoker, Invocation invocation) throws RpcException { String traceId = MDC.get(TRACE_ID_KEY); if (traceId != null && !traceId.isEmpty()) { RpcContext.getServiceContext().setObjectAttachment(TRACE_ID_KEY, traceId); } return invoker.invoke(invocation); } }
将 ConsumerDubboFilter 通过 SPI 机制注册进来。在调用端 src/main/resources/META-INF/dubbo 中写入文件:org.apache.dubbo.rpc.Filter
1 consumerDubboFilter =zdemo.scloud.user.filter.ConsumerDubboFilter
RestTemplate 支持 配置和注入 RestTemplate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package zdemo.scloud.user.config;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate (); } }
使用 RestTemplate 发起远程调用:
1 2 3 4 5 6 7 8 9 10 11 @Resource private RestTemplate restTemplate;@GetMapping("/http/user/order") public OrderDTO getUserOrderDetailsUsingRestTemplate (@RequestParam String orderNo) { log.info("使用 http 发起 RPC 请求,订单号: {}" , orderNo); String serviceName = "zdemo-scloud-order" ; String url = "http://" + serviceName + "/order/details?orderNo=" + orderNo; System.out.println("正在通过 LoadBalancer 准备投递请求至: " + url); return restTemplate.getForObject(url, OrderDTO.class); }
feign 调用支持 1 2 3 4 5 6 7 8 @Resource private OrderFeignService orderFeignService;@GetMapping("/feign/user/order") public OrderDTO getUserOrderDetailsUsingOpenFeign (@RequestParam String orderNo) { log.info("使用 feign 发起 RPC 请求,订单号: {}" , orderNo); return orderFeignService.getUserOrderDetails(orderNo); }
对于 feign 的超时、日志级别、拦截器都可以在配置文件中设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 spring: cloud: openfeign: client: config: default: logger-level: none connect-timeout: 5000 read-timeout: 5000 zdemo-scloud-order: logger-level: full connect-timeout: 5000 read-timeout: 11000 request-interceptors: - zdemo.scloud.user.filter.CustomOpenFeignInterceptor
feign 拦截器 CustomOpenFeignInterceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package zdemo.scloud.user.filter;import feign.RequestInterceptor;import feign.RequestTemplate;import jakarta.servlet.http.HttpServletRequest;import org.slf4j.MDC;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import java.util.Enumeration;public class CustomOpenFeignInterceptor implements RequestInterceptor { @Override public void apply (RequestTemplate requestTemplate) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null ) { HttpServletRequest request = attributes.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null ) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); if (!"content-length" .equalsIgnoreCase(name)) { String value = request.getHeader(name); requestTemplate.header(name, value); } } } String token = request.getHeader("Authorization" ); if (token != null ) { requestTemplate.header("Authorization" , token); } else { requestTemplate.header("Authorization" , "DEFAULT" ); } } String traceId = MDC.get("traceId" ); if (traceId != null ) { requestTemplate.header("X-B3-TraceId" , traceId); } } }
将其注入spring容器,使其生效:
1 2 3 4 5 6 7 8 spring: cloud: openfeign: client: config: zdemo-scloud-order: request-interceptors: - zdemo.scloud.user.filter.CustomOpenFeignInterceptor
标题:
Spring Cloud Alibaba 基础案例 - Nacos、OpenFeign、Dubbo 以及各自的负载均衡和拦截器使用